using System;
using System.Collections.Generic;
using System.ComponentModel;
/******************************************************************************* 
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  
 *  You may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0.html 
 *  This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
 *  CONDITIONS OF ANY KIND, either express or implied. See the License for the 
 *  specific language governing permissions and limitations under the License.
 * ***************************************************************************** 
 * 
 *  Joel Wetzel
 *  Affirma Consulting
 *  jwetzel@affirmaconsulting.com
 * 
 */

using System.Configuration;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;

using Affirma.ThreeSharp;
using Affirma.ThreeSharp.Query;
using Affirma.ThreeSharp.Model;
using Affirma.ThreeSharp.Statistics;

namespace Affirma.CloudFront.AdminTool
{
    internal sealed class AccountManager
    {
        #region Constants

        public const string SettingPrefix = "AwsAccount_";

        #endregion

        #region Variables

        private static volatile AccountManager instance;
        private static object syncRoot = new Object();
        private IThreeSharp service;
        private ThreeSharpConfig config;
        private AwsAccount useAccount;
        private string awsAccountXmlFilePath;
        private byte[] key = { };
        private byte[] IV = { 38, 55, 206, 48, 28, 64, 20, 16 };
        private string stringKey = "_dR6%!x8Utb9)";

        #endregion

        #region Constructors

        private AccountManager()
        {
            this.config = new ThreeSharpConfig();
            this.config.ConnectionLimit = 1000;

            // It is necessary to use the SUBDOMAIN CallingFormat for accessing EU buckets
            this.config.Format = CallingFormat.SUBDOMAIN;

            this.awsAccountXmlFilePath = this.GetAwsAccountXmlFilePath();
        }

        #endregion

        #region Methods

        private string GetAwsAccountXmlFilePath()
        {
            string path = string.Empty;
            path = System.IO.Path.Combine(Application.UserAppDataPath, "CamViewer");
            string[] pathParts = path.Split(System.IO.Path.DirectorySeparatorChar);
            System.Diagnostics.Debug.Assert(pathParts.Length > 3);
            string pathSep = System.Char.ToString(System.IO.Path.DirectorySeparatorChar);
            if (pathParts.Length > 3)
                path = System.String.Join(pathSep, pathParts, 0, pathParts.Length - 2);
            path += "\\AwsAccounts";

            return path;
        }

        private bool SaveAccountToFileSystem(AwsAccount account)
        {
            bool saved = false;

            try
            {
                if (!Directory.Exists(this.GetAwsAccountXmlFilePath()))
                    Directory.CreateDirectory(this.GetAwsAccountXmlFilePath());

                XmlSerializer serializer = new XmlSerializer(account.GetType());
                using (FileStream fs = new FileStream(Path.Combine(this.GetAwsAccountXmlFilePath(), SettingPrefix + account.ID.ToString()), FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    serializer.Serialize(fs, account);
                    saved = true;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }

            return saved;
        }

        private void DeleteAccountFromFileSystem(AwsAccount account)
        {
            try
            {
                if (File.Exists(Path.Combine(this.GetAwsAccountXmlFilePath(), SettingPrefix + account.ID.ToString())))
                    File.Delete(Path.Combine(this.GetAwsAccountXmlFilePath(), SettingPrefix + account.ID.ToString()));
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
        }

        private void DeleteAccountFromSettings(AwsAccount account)
        {
            try
            {
                if (Properties.Settings.Default.Properties[SettingPrefix + account.ID] != null)
                    Properties.Settings.Default.Properties.Remove(SettingPrefix + account.ID);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
        }

        private bool SaveAccountToSettings(AwsAccount account)
        {
            bool saved = false;

            try
            {
                if (Properties.Settings.Default.Properties[SettingPrefix + account.ID] == null)
                {
                    SettingsProperty prop = new SettingsProperty(SettingPrefix + account.ID);
                    prop.DefaultValue = account;
                    Properties.Settings.Default.Properties.Add(prop);
                }
                else
                    Properties.Settings.Default.Properties[SettingPrefix + account.ID].DefaultValue = account;

                Properties.Settings.Default.Save();

                saved = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }

            return saved;
        }

        private void InitService()
        {
            this.config.AwsAccessKeyID = this.useAccount.AccessKey;
            this.config.AwsSecretAccessKey = this.Decrypt(this.useAccount.SecretKey);
            this.config.IsSecure = this.useAccount.UseS3Https;
            this.service = new ThreeSharpQuery(this.config); 
        }

        public string Encrypt(string text)
        {
            try
            {
                key = Encoding.UTF8.GetBytes(stringKey.Substring(0, 8));
                DESCryptoServiceProvider des = new DESCryptoServiceProvider();
                Byte[] byteArray = Encoding.UTF8.GetBytes(text);
                MemoryStream memoryStream = new MemoryStream();
                CryptoStream cryptoStream = new CryptoStream(memoryStream,
                    des.CreateEncryptor(key, IV), CryptoStreamMode.Write);
                cryptoStream.Write(byteArray, 0, byteArray.Length);
                cryptoStream.FlushFinalBlock();
                return Convert.ToBase64String(memoryStream.ToArray());
            }
            catch (Exception ex)
            {
                MessageBox.Show("Unable to encrypt value");
            }
            return string.Empty;
        }

        public string Decrypt(string text)
        {
            try
            {
                key = Encoding.UTF8.GetBytes(stringKey.Substring(0, 8));
                DESCryptoServiceProvider des = new DESCryptoServiceProvider();
                Byte[] byteArray = Convert.FromBase64String(text);
                MemoryStream memoryStream = new MemoryStream();
                CryptoStream cryptoStream = new CryptoStream(memoryStream,
                    des.CreateDecryptor(key, IV), CryptoStreamMode.Write);
                cryptoStream.Write(byteArray, 0, byteArray.Length);
                cryptoStream.FlushFinalBlock();
                return Encoding.UTF8.GetString(memoryStream.ToArray());
            }
            catch (Exception ex)
            {
                MessageBox.Show("Unable to decrypt value");
            }

            return string.Empty;
        }

        public void DeleteAccount(AwsAccount account)
        {
            this.DeleteAccountFromSettings(account);
            this.DeleteAccountFromFileSystem(account);

            if (this.useAccount.SecretKey == account.SecretKey)
            {
                this.useAccount = null;
                this.service = null;
            }
        }

        public List<AwsAccount> GetSavedAccounts()
        {
            List<AwsAccount> accounts = new List<AwsAccount>();

            try
            {
                if (Directory.Exists(this.GetAwsAccountXmlFilePath()))
                {
                    DirectoryInfo accountDir = new DirectoryInfo(this.GetAwsAccountXmlFilePath());
                    foreach (FileInfo f in accountDir.GetFiles())
                    {
                        using(Stream Read = f.OpenRead())
                        {
                        XmlSerializer formatter = new XmlSerializer(typeof(AwsAccount));
                        AwsAccount account = (AwsAccount)formatter.Deserialize(Read);

                        if (!accounts.Contains(account))
                            accounts.Add(account);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }

            return accounts;
        }

        public bool LoadSavedAccounts()
        {
            bool useAccountSet = false;

            try
            {
                List<AwsAccount> savedAccounts = this.GetSavedAccounts();
                if (savedAccounts.Count > 0)
                {
                    if (savedAccounts.Count == 1 
                        && !savedAccounts[0].IsSelected)
                    {
                        savedAccounts[0].IsSelected = true;
                        this.SaveAccount(savedAccounts[0]);
                    }
                    else
                    {
                        foreach (AwsAccount account in savedAccounts)
                        {
                            SettingsProperty prop = new SettingsProperty(SettingPrefix + account.ID);
                            
                            prop.DefaultValue = account;
                            Properties.Settings.Default.Properties.Add(prop);
                            Properties.Settings.Default.Save();

                            if (account.IsSelected)
                            {
                                this.useAccount = account;
                                this.InitService();

                                useAccountSet = true;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

            return useAccountSet;
        }        

        public bool SaveAccount(AwsAccount saveAccount)
        {
            bool saved = false;

            try
            {
                if (saveAccount != null)
                {
                    List<AwsAccount> savedAccounts = this.GetSavedAccounts();
                    if (savedAccounts.Count < 2 && !saveAccount.IsSelected)
                    {
                        saveAccount.IsSelected = true;
                    }
                    else
                    {
                        foreach (AwsAccount account in savedAccounts)
                        {
                            if (saveAccount.IsSelected
                                && account.IsSelected
                                && account.ID != saveAccount.ID)
                            {
                                account.IsSelected = false;

                                this.SaveAccountToFileSystem(account);
                                this.SaveAccountToSettings(account);
                            }                           
                        }
                    }

                    if (saveAccount.IsSelected)
                    {
                        this.useAccount = saveAccount;                        
                        this.InitService();
                    }

                    this.SaveAccountToFileSystem(saveAccount);
                    this.SaveAccountToSettings(saveAccount);

                    saved = true;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }

            return saved;
        }

        public AwsAccount GetAwsAccountByName(string accountName)
        {
            AwsAccount awsAccount = null;

            try
            {
                foreach (SettingsProperty property in Properties.Settings.Default.Properties)
                {
                    if (property.Name.Contains(SettingPrefix)
                        && ((AwsAccount)property.DefaultValue).Name.ToLower() == accountName.ToLower())
                    {
                        awsAccount = (AwsAccount)property.DefaultValue;
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }

            return awsAccount;
        }

        public bool IsServiceSelected(bool showMessage)
        {
            bool isSelected = (this.service == null) ? false : true;

            if (!isSelected && showMessage)
                MessageBox.Show("Account has not been setup. Unable to process request.", "Configuration Error");

            return isSelected;
        }

        #endregion

        #region Properties

        public static AccountManager Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                            instance = new AccountManager();
                    }
                }

                return instance;
            }
        }

        public IThreeSharp Service
        {
            get { return this.service; }
        }

        public AwsAccount UseAccount
        {
            get { return this.useAccount; }
        }        

        #endregion
    }    
}